package com.gl.basis.common.log;


import com.alibaba.fastjson.JSONObject;
import com.gl.basis.common.pojo.Page;
import com.gl.basis.common.util.NetworkUtil;
import com.gl.basis.common.util.SessionUtil;
import com.gl.basis.common.util.ThreadLocalUtil;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.ResponseFacade;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

@Order(-1)
@Component
@Aspect
public class OperateLogAspect {

	private static final Logger logger = LoggerFactory.getLogger(OperateLogAspect.class);

	@Around("@annotation(log)")
	public Object doAround(ProceedingJoinPoint joinPoint, OperateLog log) throws Throwable {
		if (!log.enable()) {
			Object result = joinPoint.proceed();
			return result;
		}
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		String key = ThreadLocalUtil.get("traceId");
		if (key == null) {
			key = System.currentTimeMillis() + new Random().nextInt(Integer.MAX_VALUE) + "";
			ThreadLocalUtil.set("traceId", key);
		}
		OperateLogger baseLog = new OperateLogger();
		try {
			Signature signature = joinPoint.getSignature();
			Class<?> clazz = joinPoint.getTarget().getClass();
			if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(RestController.class)) {
				HttpServletRequest request = null;
				try {
					request = SessionUtil.getRequest();
				} catch (Exception e) {
					e.printStackTrace();
				}
				if (request != null) {
					baseLog.setRequestUri(request.getRequestURI());
					baseLog.setIp(NetworkUtil.getIpAddress(request));
				}
			}
			baseLog.setTraceId(key);
			baseLog.setClassName(clazz.getName());
			baseLog.setMethodName(signature.getName());
			Map<String, Object> params = handlerParams(joinPoint);
			baseLog.setParams(params);
			//ASysUser user = SessionUtil.getUserAllowNull();
			//baseLog.setUserId(user == null ? "anonymous" : user.getLoginName());
			Object result = joinPoint.proceed();
			Map<String, Object> ret = handlerResult(result);
			baseLog.setResult(ret);
			return result;
		} catch (Throwable e) {
			baseLog.setException(handlerException(e));
			throw e;
		} finally {
			stopWatch.stop();
			baseLog.setCostTime(stopWatch.getTotalTimeSeconds());
			logger.info(JSONObject.toJSONString(baseLog, true));
		}
	}

	private Map<String, Object> handlerResult(Object result) {
		Map<String, Object> ret = new HashMap<>();
		try {
			if (result == null) {
				return ret;
			}
			if (Page.class.isAssignableFrom(result.getClass())) {
				Page page = (Page) result;
				ret.put("page[code]", page.getCode());
				ret.put("page[msg]", page.getMsg());
				ret.put("page[map]", page.getMap());
				ret.put("page[T]", page.getT());
				ret.put("page[list]", page.getList());
			} else {
				ret.put("value", result);
			}
			return ret;
		} catch (Exception e) {
			List<String> ex = handlerException(e);
			ret.put("exception", ex);
			return ret;
		}


	}

	private Map<String, Object> handlerParams(ProceedingJoinPoint joinPoint) {
		HashMap<String, Object> paramMap = new HashMap();
		try {
			String classType = joinPoint.getTarget().getClass().getName();
			String methodName = joinPoint.getSignature().getName();
			// 参数值
			Object[] args = joinPoint.getArgs();
			String[] paramsName = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
			// 通过map封装参数和参数值
			for (int k = 0; k < args.length; k++) {
				if (RequestFacade.class.isAssignableFrom(args[k].getClass())) {
					HttpServletRequest request = (HttpServletRequest) args[k];
					Enumeration paramNames = request.getParameterNames();
					Map<String, Object> req = new HashMap<>();
					while (paramNames.hasMoreElements()) {
						String paramName = (String) paramNames.nextElement();
						String[] paramValues = request.getParameterValues(paramName);
						if (paramValues.length == 1) {
							String paramValue = paramValues[0];
							if (paramValue.length() != 0) {
								req.put(paramName, paramValue);
							}
						}
					}
					paramMap.put("request", req);
					continue;
				}
				if (ResponseFacade.class.isAssignableFrom(args[k].getClass())) {
					continue;
				}
				if (Page.class.isAssignableFrom(args[k].getClass())) {
					Page page = (Page) args[k];
					Map<String, Object> ps = page.getParams();
					paramMap.put("page[params]", ps);
					continue;
				}
				if (args[k].getClass().isPrimitive()) {
					// 获取的是封装类型而不是基础类型
					String result = args[k].getClass().getName();
					Class clazz = map.get(result);
					ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
					// 获取指定的方法，第二个参数可以不传，但是为了防止有重载的现象，还是需要传入参数的类型
					Method method = Class.forName(classType).getMethod(methodName, clazz);
					// 参数名
					String[] parameterNames = pnd.getParameterNames(method);
					for (int i = 0; i < parameterNames.length; i++) {
						paramMap.put(parameterNames[i], args[i]);
					}
					continue;
				}
				paramMap.put(paramsName[k], args[k]);
			}


			return paramMap;
		} catch (Exception e) {
			List<String> ex = handlerException(e);
			paramMap.put("exception", ex);
			return paramMap;
		}
	}

	private static HashMap<String, Class> map = new HashMap<String, Class>() {
		{
			put("java.lang.Integer", int.class);
			put("java.lang.Double", double.class);
			put("java.lang.Float", float.class);
			put("java.lang.Long", long.class);
			put("java.lang.Short", short.class);
			put("java.lang.Boolean", boolean.class);
			put("java.lang.Char", char.class);
		}
	};

	private List<String> handlerException(Throwable e) {

		List<String> ex = new ArrayList<>();
		ex.add(e.getMessage());
		StackTraceElement[] stackArray = e.getStackTrace();
		for (StackTraceElement element : stackArray) {
			ex.add(element.toString());
		}
		return ex;
	}
}